#ifndef OHOS_RIVE_MPF_WORKER_IMPL_H
#define OHOS_RIVE_MPF_WORKER_IMPL_H
#include <variant>

#include "canvas_renderer.h"
#include "helpers/thread_state_pls.h"
#include "helpers/tracer.h"
#include "refs.h"
#include "rive/renderer/rive_renderer.hpp"
#include <native_window/external_window.h>

// Either EGLNativeWindowType* or a NAPI surface.
// std::monostate holds an 'empty' variant.
using SurfaceVariant = std::variant<std::monostate, EGLNativeWindowType, napi_ref>;
namespace ohos_rive {
struct TSFNData {
    enum class Type { ADVANCE, DRAW, DISPOSE };
    Type type;
    std::chrono::high_resolution_clock::time_point frameTime;
    float elapsedMs = 0.0f;
    std::function<void()> completionCallback = nullptr;
};
class WorkerImpl {
public:
    static std::unique_ptr<WorkerImpl> Make(SurfaceVariant, DrawableThreadState *, RendererType);

    virtual ~WorkerImpl()
    {
        // Call destroy() first!
        assert(!m_isStarted);
    }

    void start(std::chrono::high_resolution_clock::time_point frameTime);

    void stop();

    void doFrame(ITracer *tracer,
                 DrawableThreadState *threadState,
                 std::chrono::high_resolution_clock::time_point frameTime,
                 napi_threadsafe_function worker_tsfn = nullptr);

    virtual void prepareForDraw(DrawableThreadState *) const = 0;

    virtual void destroy(DrawableThreadState *) = 0;

    virtual void flush(DrawableThreadState *) const = 0;

    [[nodiscard]] virtual rive::Renderer *renderer() const = 0;

protected:
    napi_env m_env = nullptr;
    napi_ref m_napiRendererRef = nullptr;
    napi_value m_napiDrawCallback = nullptr;
    napi_value m_napiAdvanceCallback = nullptr;
    std::chrono::high_resolution_clock::time_point m_lastFrameTime;
    bool m_isStarted = false;
};

class EGLWorkerImpl : public WorkerImpl {
public:
    ~EGLWorkerImpl() override
    {
        // Call destroy() first!
        assert(m_eglSurface == EGL_NO_SURFACE);
    }

    void destroy(DrawableThreadState *threadState) override
    {
        if (m_eglSurface != EGL_NO_SURFACE) {
            auto eglThreadState = static_cast<EGLThreadState *>(threadState);
            eglThreadState->destroySurface(m_eglSurface);
            m_eglSurface = EGL_NO_SURFACE;
        }
    }

    void prepareForDraw(DrawableThreadState *threadState) const override
    {
        auto eglThreadState = static_cast<EGLThreadState *>(threadState);
        // Bind context to this thread.
        eglThreadState->makeCurrent(m_eglSurface);
        clear(threadState);
    }

    virtual void clear(DrawableThreadState *) const = 0;

protected:
    EGLWorkerImpl(EGLNativeWindowType window, DrawableThreadState *threadState, bool *success)
    {
        *success = false;
        auto eglThreadState = static_cast<EGLThreadState *>(threadState);
        m_eglSurface = eglThreadState->createEGLSurface(window);
        if (m_eglSurface == EGL_NO_SURFACE)
            return;
        *success = true;
    }

    void *m_eglSurface = EGL_NO_SURFACE;
};

class PLSWorkerImpl : public EGLWorkerImpl {
public:
    PLSWorkerImpl(EGLNativeWindowType, DrawableThreadState *, bool *success);

    void destroy(DrawableThreadState *threadState) override;

    void clear(DrawableThreadState *threadState) const override;

    void flush(DrawableThreadState *threadState) const override;

    [[nodiscard]] rive::Renderer *renderer() const override;

private:
    rive::rcp<rive::gpu::RenderTargetGL> m_renderTarget;

    std::unique_ptr<rive::RiveRenderer> m_plsRenderer;

    // Cast away [threadState] to the the thread state expected by this
    // implementation.
    static PLSThreadState *PlsThreadState(DrawableThreadState *threadState)
    {
        // Quite hacky, but this is a way to sort this out in C++ without
        // RTTI...
        return static_cast<PLSThreadState *>(threadState);
    }
};

class CanvasWorkerImpl : public WorkerImpl {
public:
    CanvasWorkerImpl(napi_ref napiSurface, bool *success)
        : m_canvasRenderer{std::make_unique<CanvasRenderer>()}, m_napiSurfaceRef{napiSurface}
    {
        *success = true;
    }

    ~CanvasWorkerImpl() override
    {
        // Call destroy() first!
        assert(m_napiSurfaceRef == nullptr);
    }

    [[nodiscard]] rive::Renderer *renderer() const override
    {
        return m_canvasRenderer.get();
    }

    void flush(DrawableThreadState *) const override;

    void prepareForDraw(DrawableThreadState *) const override;

    void destroy(DrawableThreadState *) override;

private:
    std::unique_ptr<CanvasRenderer> m_canvasRenderer;
    napi_ref m_napiSurfaceRef = nullptr;
};

} // namespace ohos_rive
#endif // OHOS_RIVE_MPF_WORKER_IMPL_H